/******************************************************************************
 * Copyright 2020-2021 The Firmament Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *****************************************************************************/

#include <firmament.h>
#include "device.h"
#include "f28p65x_device.h"

#include "drv_i2c_soft.h"
#include "hal/i2c/i2c.h"
#include "hal/i2c/i2c_bit_ops.h"

#define I2C_DELAY_US      (10)
#define I2C_TIMEOUT_TICKS TICKS_FROM_MS(1)

static struct rt_i2c_bus i2c0_dev;

#define __I2C_PIN_GPIO_INIT(i2c_base, sda, scl)       \
    {                                                 \
        .sda_pin     = sda,                           \
        .sda_pin_mux = GPIO_##sda##_##GPIO##sda,      \
        .scl_pin     = scl,                           \
        .scl_pin_mux = GPIO_##scl##_##GPIO##scl,      \
    }

enum i2c_bus_id_e {
    I2C_BUS_0,
    I2C_BUS_1,
    I2C_BUS_2,
};

typedef struct __i2c_gpio_config_t {
    uint32_t sda_pin;
    uint32_t sda_pin_mux;
    uint32_t scl_pin;
    uint32_t scl_pin_mux;
} i2c_gpio_t;

static i2c_gpio_t i2c_gpio[] = {
    // ----------------------------- base  sda  scl
    [I2C_BUS_0] = __I2C_PIN_GPIO_INIT(I2CA, 42, 43), // I2CA: SDA->GPIO42, SCL->GPIO43
};

void gd32_set_sda(void* data, rt_int32_t state)
{
    struct rt_i2c_bus* i2c_bus = (struct rt_i2c_bus*)data;

    if (i2c_bus == &i2c0_dev) {
        i2c_gpio_t *pgpio = &i2c_gpio[I2C_BUS_0];

        if (state == 1)
            GPIO_writePin(pgpio->sda_pin, 1);
        else if (state == 0)
            GPIO_writePin(pgpio->sda_pin, 0);
    }
}

void gd32_set_scl(void* data, rt_int32_t state)
{
    struct rt_i2c_bus* i2c_bus = (struct rt_i2c_bus*)data;

    if (i2c_bus == &i2c0_dev) {
        i2c_gpio_t *pgpio = &i2c_gpio[I2C_BUS_0];

        if (state == 1)
            GPIO_writePin(pgpio->scl_pin, 1);
        else if (state == 0)
            GPIO_writePin(pgpio->scl_pin, 0);
    }
}

rt_int32_t gd32_get_sda(void* data)
{
    rt_int32_t val;
    struct rt_i2c_bus* i2c_bus = (struct rt_i2c_bus*)data;

    if (i2c_bus == &i2c0_dev) {
        i2c_gpio_t *pgpio = &i2c_gpio[I2C_BUS_0];

        GPIO_setDirectionMode(pgpio->sda_pin, GPIO_DIR_MODE_IN);
        val = (rt_int32_t)GPIO_readPin(pgpio->sda_pin);
        GPIO_setDirectionMode(pgpio->sda_pin, GPIO_DIR_MODE_OUT);
    }

    return val;
}

rt_int32_t gd32_get_scl(void* data)
{
    rt_int32_t val;
    struct rt_i2c_bus* i2c_bus = (struct rt_i2c_bus*)data;

    if (i2c_bus == &i2c0_dev) {
        i2c_gpio_t *pgpio = &i2c_gpio[I2C_BUS_0];

        GPIO_setDirectionMode(pgpio->scl_pin, GPIO_DIR_MODE_IN);
        val = (rt_int32_t)GPIO_readPin(pgpio->scl_pin);
        GPIO_setDirectionMode(pgpio->scl_pin, GPIO_DIR_MODE_OUT);
    }

    return val;
}

rt_inline void gd32_udelay(rt_uint32_t us)
{
    systime_udelay(us);
}

static rt_err_t i2c_pin_init(struct rt_i2c_bus* i2c_bus)
{
    if (i2c_bus == &i2c0_dev) {
        i2c_gpio_t *pgpio = &i2c_gpio[I2C_BUS_0];

        // printf("I2C gpio: sda=%d, scl=%d \r\n", pgpio->sda_pin, pgpio->scl_pin);

        GPIO_setPadConfig(pgpio->sda_pin, GPIO_PIN_TYPE_OD);
        GPIO_setDirectionMode(pgpio->sda_pin, GPIO_DIR_MODE_OUT);
        GPIO_setQualificationMode(pgpio->sda_pin, GPIO_QUAL_ASYNC);
        GPIO_setPinConfig(pgpio->sda_pin_mux);

        GPIO_setPadConfig(pgpio->scl_pin, GPIO_PIN_TYPE_OD);
        GPIO_setDirectionMode(pgpio->scl_pin, GPIO_DIR_MODE_OUT);
        GPIO_setQualificationMode(pgpio->scl_pin, GPIO_QUAL_ASYNC);
        GPIO_setPinConfig(pgpio->scl_pin_mux);
    } else {
        return RT_EINVAL;
    }

    return RT_EOK;
}

struct rt_i2c_bit_ops gd32_i2c0_bit_ops = {
    (void*)NULL,
    gd32_set_sda,
    gd32_set_scl,
    gd32_get_sda,
    gd32_get_scl,
    gd32_udelay,
    I2C_DELAY_US,
    I2C_TIMEOUT_TICKS
};

/* i2c device instances */
static struct rt_i2c_device i2c0_dev0 = {
    .slave_addr = 0x45, /* AW2023 7 bit address */
    .flags = 0
};

static struct rt_i2c_device i2c0_dev1 = {
    .slave_addr = 0x28, /* MS4525 7 bit address */
    .flags = 0
};

rt_err_t drv_i2c_soft_init(void)
{
    gd32_i2c0_bit_ops.data = &i2c0_dev;
    i2c0_dev.priv = (void*)&gd32_i2c0_bit_ops;
    i2c0_dev.retries = 3;

    i2c_pin_init(&i2c0_dev);
    rt_i2c_soft_bus_register(&i2c0_dev, "i2c0");

    /* attach i2c devices */
    RT_TRY(rt_i2c_bus_attach_device(&i2c0_dev0, "i2c0_dev0", "i2c0", RT_NULL));

    /* attach i2c devices */
    RT_TRY(rt_i2c_bus_attach_device(&i2c0_dev1, "i2c0_dev1", "i2c0", RT_NULL));

    return RT_EOK;
}
